package com.sfang.common.base.exception;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sfang.common.base.response.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.util.StreamUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


@ControllerAdvice
@Slf4j
@ConditionalOnMissingClass(value = "com.aurora.common.gateway.SaGatewayApplication")
public class GlobalExceptionHandler implements BlockExceptionHandler {

    @Resource
    Environment env;

    @ExceptionHandler(value = NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ResponseBody
    public Response<Void> errorHandler(HttpServletRequest request, NoHandlerFoundException exception, HttpServletResponse response) {
        buildRequestInfo(request, "ErrorHandler#NoHandlerFoundException", exception);
        return Response.error(HttpStatus.NOT_FOUND.value(), "请求路径不正确！");
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseBody
    public Response<Void> errorHandler(HttpServletRequest request, HttpRequestMethodNotSupportedException exception, HttpServletResponse response) {
        buildRequestInfo(request, "ErrorHandler#HttpRequestMethodNotSupportedException", exception);
        return Response.error(HttpStatus.METHOD_NOT_ALLOWED.value(), "方法类型不一致！");
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseBody
    public Response<Void> errorHandler(HttpServletRequest request, HttpMediaTypeNotSupportedException exception, HttpServletResponse response) {
        buildRequestInfo(request, "ErrorHandler#HttpMediaTypeNotSupportedException", exception);
        return Response.error(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), "Content-Type类型不一致！");
    }

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    @ResponseBody
    public Response<Void> fileSizeExceededException(HttpServletRequest request, MaxUploadSizeExceededException exception, HttpServletResponse response) {
        buildRequestInfo(request, "ErrorHandler#MaxUploadSizeExceededException", exception);
        return Response.error(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED.value(), "上传文件过大！");
    }

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Response<Void> errorHandler(HttpServletRequest request, Exception exception, HttpServletResponse response) {
        buildRequestInfo(request, "ErrorHandler#Exception", exception);
        return Response.error(exception.getMessage());
    }

    @ExceptionHandler(value = {BusinessException.class})
    @ResponseBody
    private Response<Void> processException(HttpServletRequest request, BusinessException exception, HttpServletResponse response) {
        buildRequestInfo(request, "ErrorHandler#BusinessException", exception);
        return Response.error(exception.getCode(), exception.getMessage());
    }

    /**
     * @author lilei
     * @since 2021-10-20
     * 提示的错误不用打到error log
     */
    @ExceptionHandler(value = {MethodArgumentNotValidException.class,
            ConstraintViolationException.class,
            IllegalArgumentException.class,
            MissingServletRequestParameterException.class})
    @ResponseBody
    private Response<Void> processException(HttpServletRequest request, Exception ex, HttpServletResponse response) {
        String exName = ex.getClass().getSimpleName();

        //只有线上会有env参数
        String environment = env.getProperty("env");

        if (environment != null && environment.toLowerCase() == "prod") {
            buildInfoLog(request, exName, ex);
        } else {
            buildRequestInfo(request, "ErrorHandler#" + exName, ex);
        }

        switch (exName) {
            case "MethodArgumentNotValidException":
                MethodArgumentNotValidException mEx = (MethodArgumentNotValidException) ex;
                List<FieldError> fieldErrors = mEx.getBindingResult().getFieldErrors();
                String error = fieldErrors.stream()
                        .map(DefaultMessageSourceResolvable::getDefaultMessage)
                        .collect(Collectors.joining(";"));
                return Response.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), error);
            case "ConstraintViolationException":
            case "IllegalArgumentException":
            case "MissingServletRequestParameterException":
                return Response.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage());
            default:
                return Response.error(ex.getMessage());
        }

    }

    /**
     * 表单验证异常处理
     */
    @ExceptionHandler(value = BindException.class)
    @ResponseBody
    public Response<Void> validExceptionHandler(BindException exception, HttpServletRequest request, HttpServletResponse response) {
        buildRequestInfo(request, "ErrorHandler#BindException", exception);
        List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
        Map<String, String> errors = new HashMap<>();
        for (FieldError error : fieldErrors) {
            errors.put(error.getField(), error.getDefaultMessage());
        }
        return Response.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), exception.getClass().getSimpleName() + " : " + JSON.toJSONString(errors));
    }


    /**
     * @author lilei
     * @since 2021-10-20
     * 只记录日志信息,不打印错误
     */
    private void buildInfoLog(HttpServletRequest request, String exceptionType, Exception e) {
        String url = request.getRequestURL().toString(),
                parameterStringValue = "";
        try {
            parameterStringValue = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
        } catch (IOException err) {
            err.printStackTrace();
        }

        log.error(String.format("%s:url-%s %s %s %s %s %s",
                exceptionType,
                url,
                StringUtils.isNotEmpty(parameterStringValue) ? "params-【" + parameterStringValue + "】" : "",
                "headers-【" + buildRequestHeader(request) + "】",
                buildRequestParam(request),
                buildBusinessMsg(),
                e == null ? "" : "error:" + e.getMessage()));
    }

    private void buildRequestInfo(HttpServletRequest request, String exceptionType, Exception e) {
        String url = request.getRequestURL().toString(),
                parameterStringValue = "";
        try {
            parameterStringValue = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
        } catch (IOException err) {
            err.printStackTrace();
        }

        log.error(String.format("%s:url-%s %s %s %s %s %s",
                exceptionType,
                url,
                StringUtils.isNotEmpty(parameterStringValue) ? "params-【" + parameterStringValue + "】" : "",
                "headers-【" + buildRequestHeader(request) + "】",
                buildRequestParam(request),
                buildBusinessMsg(),
                e == null ? "" : "error:" + e.getMessage()), e);
    }

    private String buildBusinessMsg() {
        return "";
    }

    private String buildRequestHeader(HttpServletRequest httpServletRequest) {
        try {
            Map<String, String> headers = new HashMap<>();

            if (httpServletRequest == null)
                return "";

            Enumeration<String> enumeration = httpServletRequest.getHeaderNames();
            while (enumeration.hasMoreElements()) {
                String headerKey = enumeration.nextElement();

                if (headerKey.trim().equalsIgnoreCase("authorization") ||
                        headerKey.trim().equalsIgnoreCase("tokenPayload"))
                    continue;

                String headerVal = httpServletRequest.getHeader(headerKey);
                headers.put(headerKey, headerVal);
            }

            String headerVals = JSON.toJSONString(headers, com.alibaba.fastjson.serializer.SerializerFeature.SortField);
            return headerVals.length() > 1000 ? headerVals.substring(0, 1000) : headerVals;
        } catch (Exception e) {
            log.error(String.format("globalExceptionHandler buildRequestHeader error:%s", e.getMessage()), e);
            return "";
        }
    }

    private String buildRequestParam(HttpServletRequest httpServletRequest) {
        try {

            if (httpServletRequest.getMethod() == null || !httpServletRequest.getMethod().equalsIgnoreCase("GET"))
                return "";

            Map<String, String> params = new HashMap<>();

            Enumeration<String> enumeration = httpServletRequest.getParameterNames();
            while (enumeration.hasMoreElements()) {
                String paramKey = enumeration.nextElement();
                String paramVal = httpServletRequest.getParameter(paramKey);
                params.put(paramKey, paramVal);
            }

            String paramVals = JSON.toJSONString(params, com.alibaba.fastjson.serializer.SerializerFeature.SortField);
            return "queryString-【" + (paramVals.length() > 1000 ? paramVals.substring(0, 1000) : paramVals) + "】";
        } catch (Exception e) {
            log.error(String.format("globalExceptionHandler buildRequestParam error:%s", e.getMessage()), e);
            return "";
        }
    }

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {

        Response<Void> response = null;

        if (e instanceof FlowException) {
            buildRequestInfo(httpServletRequest, "ErrorHandler#FlowException", e);
            response = Response.error(SentinelExceptionType.SENTINEL_FLOW_ERROR.getCode(), SentinelExceptionType.SENTINEL_FLOW_ERROR.getDesc());
        } else if (e instanceof ParamFlowException) {
            buildRequestInfo(httpServletRequest, "ErrorHandler#ParamFlowException", e);
            response = Response.error(SentinelExceptionType.SENTINEL_PARAM_FLOW_ERROR.getCode(), SentinelExceptionType.SENTINEL_PARAM_FLOW_ERROR.getDesc());
        } else if (e instanceof DegradeException) {
            buildRequestInfo(httpServletRequest, "ErrorHandler#DegradeException", e);
            response = Response.error(SentinelExceptionType.SENTINEL_DEGRADE_ERROR.getCode(), SentinelExceptionType.SENTINEL_DEGRADE_ERROR.getDesc());
        } else if (e instanceof AuthorityException) {
            buildRequestInfo(httpServletRequest, "ErrorHandler#AuthorityException", e);
            response = Response.error(SentinelExceptionType.SENTINEL_AUTHORITY_ERROR.getCode(), SentinelExceptionType.SENTINEL_AUTHORITY_ERROR.getDesc());
        } else if (e instanceof SystemBlockException) {
            buildRequestInfo(httpServletRequest, "ErrorHandler#SystemBlockException", e);
            response = Response.error(SentinelExceptionType.SENTINEL_SYSTEM_ERROR.getCode(), SentinelExceptionType.SENTINEL_SYSTEM_ERROR.getDesc());
        } else {
            buildRequestInfo(httpServletRequest, "ErrorHandler#BlockException", e);
            response = Response.error(SentinelExceptionType.SENTINEL_UNKNOWN_ERROR.getCode(), SentinelExceptionType.SENTINEL_UNKNOWN_ERROR.getDesc());
        }

        httpServletResponse.setContentType("application/json; charset=utf-8");
        new ObjectMapper().writeValue(httpServletResponse.getWriter(), response);
    }
}
